﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace Webcam.Image
{
    /// <summary>
    /// An image represented locally
    /// </summary>
    public class RGBImage
    {
        /// <summary>The data held by this image</summary>
        /// <remarks>data[y][x]</remarks>
        private RGB[][] data;

        /// <summary>The width of the image</summary>
        private int _width;
        /// <summary>The height of the image</summary>
        private int _height;

        /// <summary>The width of the image</summary>
        public int Width { get { return _width; } }
        /// <summary>The height of the image</summary>
        public int Height { get { return _height; } }


        public RGBImage(Bitmap b)
        {
            init(b.Width, b.Height);
            data = toRBG(b);
        }

        /// <summary>
        /// Create an RGBImage
        /// </summary>
        /// <param name="width">Width of the image</param>
        /// <param name="height">Height of the image</param>
        public RGBImage(int width, int height)
            : this (width,height, RGB.White)
        {
        }

        public RGBImage(int width, int height, RGB defaultColor)
        {
            init(width, height);
            this.data = new RGB[height][];
            for (int i = 0; i < height; i++)
            {
                this.data[i] = new RGB[width];
                for (int j = 0; j < width; j++)
                {
                    this.data[i][j] = defaultColor;
                }
            }
        }

        private RGBImage(RGB[][] data)
        {
            init (data[0].Length,data.Length);
            this.data = data;
        }

        private void init(int width, int height)
        {
            this._height = height;
            this._width = width;
        }

        /// <summary>
        /// Return a copy of this object
        /// </summary>
        /// <returns></returns>
        public RGBImage Clone()
        {
            RGB[][] objs = new RGB[data.Length][];
            for (int i = 0; i < data.Length; i++)
            {
                int s = this.data[i].Length;
                objs[i] = new RGB[s];
                for (int j = 0; j < s; j++)
                {
                    objs[i][j] = this.data[i][j];
                }
            }

            return new RGBImage(objs);
        }

        public RGB this[int x,int y] 
        {
            get { return data[y][x];}
            set { data[y][x] = value; }
        }

        public RGB this[Point p]
        {
            get { return this[p.X, p.Y]; }
            set { this[p.X, p.Y] = value; }
        }


        /// <summary>
        /// Convert a bitmap into a RBG array
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        unsafe static RGB[][] toRBG(Bitmap b)
        {
            BitmapData data = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

            RGB[][] colors = new RGB[b.Height][];
            for (int i = 0; i < b.Height; i++)
                colors[i] = new RGB[b.Width];

            int pixelSize = 3;

            for (int y = 0; y < b.Height; y++)
            {
                byte* pos = (byte*)data.Scan0 + (y * data.Stride);

                for (int x = 0; x < b.Width; x++)
                {
                    byte red = pos[x * pixelSize + 2];  // R;
                    byte green = pos[x * pixelSize + 1];// G;
                    byte blue = pos[x * pixelSize];     // B;
                 
                    colors[y][x] = new RGB(red,green,blue);
                }
            }

            b.UnlockBits(data);

            return colors;
        }

        /// <summary>Converts the currect RGBImage to grayscale</summary>
        /// <returns></returns>
        public void ToGrayScale()
        {
            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    this[x,y] = new RGB(this.data[y][x].GrayScale);
                }
            }
        }

        /// <summary>Convert this RGBImage to a bitmap</summary>
        /// <returns>The bitmap version</returns>
        public Bitmap ToBitmap()
        {
            return ToBitmap(this.data);
        }

        /// <summary>Convert an array of RGB values into a Bitmap</summary>
        /// <param name="c"></param>
        /// <returns></returns>
        unsafe static Bitmap ToBitmap(RGB[][] c)
        {
            int height = c.Length;
            if (height <= 0)
                throw new ArgumentOutOfRangeException("Height of data is too small. c[Height][Width] is the format");
            int width = c[0].Length;
            if (width <= 0)
                throw new ArgumentOutOfRangeException("Width of data is too small. c[Height][Width] is the format");


            Bitmap b = new Bitmap(width, height);
            BitmapData data = b.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // bytes per pixel = 3
            int BPP = 3;

            // move the data from the RGB array into the byte area
            for (int y = 0; y < b.Height; y++)
            {
                byte* pos = (byte*)data.Scan0 + (y * data.Stride);

                for (int x = 0; x < b.Width; x++)
                {
                    RGB co = c[y][x];
                    pos[x * BPP] = co.blue;         // B
                    pos[x * BPP + 1] = co.green;    // G
                    pos[x * BPP + 2] = co.red;      // R
                }
            }

            b.UnlockBits(data);
            return b;
        }

    }
}
